/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package wx.xml.xalan.xpath.axes;

import java.util.Vector;

import wx.xml.xalan.xalan.res.XSLMessages;
import wx.xml.xalan.xml.dtm.DTM;
import wx.xml.xalan.xml.dtm.DTMAxisTraverser;
import wx.xml.xalan.xml.dtm.DTMIterator;
import wx.xml.xalan.xpath.Expression;
import wx.xml.xalan.xpath.ExpressionOwner;
import wx.xml.xalan.xpath.XPathContext;
import wx.xml.xalan.xpath.XPathVisitor;
import wx.xml.xalan.xpath.compiler.Compiler;
import wx.xml.xalan.xpath.res.XPATHErrorResources;

/**
 * Serves as common interface for axes Walkers, and stores common
 * state variables.
 */
public class AxesWalker extends PredicatedNodeTest
    implements Cloneable, PathComponent, ExpressionOwner {
    static final long serialVersionUID = -2966031951306601247L;
    /**
     * The next walker in the location step chain.
     *
     * @serial
     */
    protected AxesWalker m_nextWalker;
    /**
     * The traversal axis from where the nodes will be filtered.
     */
    protected int m_axis = -1;
    /**
     * The DTM inner traversal class, that corresponds to the super axis.
     */
    protected DTMAxisTraverser m_traverser;
    /**
     * The root node of the TreeWalker, as specified when it was created.
     */
    transient int m_root = DTM.NULL;
    /**
     * True if an itteration has not begun.
     */
    transient boolean m_isFresh;
    /**
     * The previous walker in the location step chain, or null.
     *
     * @serial
     */
    AxesWalker m_prevWalker;
    /**
     * The DTM for the root.  This can not be used, or must be changed,
     * for the filter walker, or any walker that can have nodes
     * from multiple documents.
     * Never, ever, access this value without going through getDTM(int node).
     */
    private DTM m_dtm;

    //=============== TreeWalker Implementation ===============
    /**
     * The node at which the TreeWalker is currently positioned.
     */
    private transient int m_currentNode = DTM.NULL;

    /**
     * Construct an AxesWalker using a LocPathIterator.
     *
     * @param locPathIterator non-null reference to the parent iterator.
     */
    public AxesWalker(LocPathIterator locPathIterator, int axis) {
        super(locPathIterator);
        m_axis = axis;
    }

    /**
     * Find a clone that corresponds to the key argument.
     *
     * @param key       The original AxesWalker for which there may be a clone.
     * @param cloneList vector of sources in odd elements, and the
     *                  corresponding clones in even vectors, may be null.
     * @return A clone that corresponds to the key, or null if key not found.
     */
    static AxesWalker findClone(AxesWalker key, Vector cloneList) {
        if (null != cloneList) {
            // First, look for clone on list.
            int n = cloneList.size();
            for (int i = 0; i < n; i += 2) {
                if (key == cloneList.elementAt(i))
                    return (AxesWalker) cloneList.elementAt(i + 1);
            }
        }
        return null;
    }

    public final WalkingIterator wi() {
        return (WalkingIterator) m_lpi;
    }

    /**
     * Initialize an AxesWalker during the parse of the XPath expression.
     *
     * @param compiler The Compiler object that has information about this
     *                 walker in the op map.
     * @param opPos    The op code position of this location step.
     * @param stepType The type of location step.
     * @throws javax.xml.transform.TransformerException
     */
    public void init(Compiler compiler, int opPos, int stepType)
        throws javax.xml.transform.TransformerException {

        initPredicateInfo(compiler, opPos);

        // int testType = compiler.getOp(nodeTestOpPos);
    }

    /**
     * Get a cloned AxesWalker.
     *
     * @return A new AxesWalker that can be used without mutating this one.
     * @throws CloneNotSupportedException
     */
    public Object clone() throws CloneNotSupportedException {
        // Do not access the location path itterator during this operation!

        AxesWalker clone = (AxesWalker) super.clone();

        //clone.setCurrentNode(clone.m_root);

        // clone.m_isFresh = true;

        return clone;
    }

    /**
     * Do a deep clone of this walker, including next and previous walkers.
     * If the this AxesWalker is on the clone list, don't clone but
     * return the already cloned version.
     *
     * @param cloneOwner non-null reference to the cloned location path
     *                   iterator to which this clone will be added.
     * @param cloneList  non-null vector of sources in odd elements, and the
     *                   corresponding clones in even vectors.
     * @return non-null clone, which may be a new clone, or may be a clone
     * contained on the cloneList.
     */
    AxesWalker cloneDeep(WalkingIterator cloneOwner, Vector cloneList)
        throws CloneNotSupportedException {
        AxesWalker clone = findClone(this, cloneList);
        if (null != clone)
            return clone;
        clone = (AxesWalker) this.clone();
        clone.setLocPathIterator(cloneOwner);
        if (null != cloneList) {
            cloneList.addElement(this);
            cloneList.addElement(clone);
        }

        if (wi().m_lastUsedWalker == this)
            cloneOwner.m_lastUsedWalker = clone;

        if (null != m_nextWalker)
            clone.m_nextWalker = m_nextWalker.cloneDeep(cloneOwner, cloneList);

        // If you don't check for the cloneList here, you'll go into an
        // recursive infinate loop.
        if (null != cloneList) {
            if (null != m_prevWalker)
                clone.m_prevWalker = m_prevWalker.cloneDeep(cloneOwner, cloneList);
        } else {
            if (null != m_nextWalker)
                clone.m_nextWalker.m_prevWalker = clone;
        }
        return clone;
    }

    /**
     * Detaches the walker from the set which it iterated over, releasing
     * any computational resources and placing the iterator in the INVALID
     * state.
     */
    public void detach() {
        m_currentNode = DTM.NULL;
        m_dtm = null;
        m_traverser = null;
        m_isFresh = true;
        m_root = DTM.NULL;
    }

    /**
     * The root node of the TreeWalker, as specified in setRoot(int root).
     * Note that this may actually be below the current node.
     *
     * @return The context node of the step.
     */
    public int getRoot() {
        return m_root;
    }

    /**
     * Set the root node of the TreeWalker.
     * (Not part of the DOM2 TreeWalker interface).
     *
     * @param root The context node of this step.
     */
    public void setRoot(int root) {
        // %OPT% Get this directly from the lpi.
        XPathContext xctxt = wi().getXPathContext();
        m_dtm = xctxt.getDTM(root);
        m_traverser = m_dtm.getAxisTraverser(m_axis);
        m_isFresh = true;
        m_foundLast = false;
        m_root = root;
        m_currentNode = root;

        if (DTM.NULL == root) {
            throw new RuntimeException(
                XSLMessages.createXPATHMessage(XPATHErrorResources.ER_SETTING_WALKER_ROOT_TO_NULL, null)); //"\n !!!! Error! Setting the root of a walker to null!!!");
        }

        resetProximityPositions();
    }

    /**
     * Get the analysis bits for this walker, as defined in the WalkerFactory.
     *
     * @return One of WalkerFactory#BIT_DESCENDANT, etc.
     */
    public int getAnalysisBits() {
        int axis = getAxis();
        int bit  = WalkerFactory.getAnalysisBitFromAxes(axis);
        return bit;
    }

    //============= End TreeWalker Implementation =============

    /**
     * The node at which the TreeWalker is currently positioned.
     * <br> The value must not be null. Alterations to the DOM tree may cause
     * the current node to no longer be accepted by the TreeWalker's
     * associated filter. currentNode may also be explicitly set to any node,
     * whether or not it is within the subtree specified by the root node or
     * would be accepted by the filter and whatToShow flags. Further
     * traversal occurs relative to currentNode even if it is not part of the
     * current view by applying the filters in the requested direction (not
     * changing currentNode where no traversal is possible).
     *
     * @return The node at which the TreeWalker is currently positioned, only null
     * if setRoot has not yet been called.
     */
    public final int getCurrentNode() {
        return m_currentNode;
    }

    //============= State Data =============

    /**
     * Get the next walker in the location step chain.
     *
     * @return Reference to AxesWalker derivative, or null.
     */
    public AxesWalker getNextWalker() {
        return m_nextWalker;
    }

    /**
     * Set the next walker in the location step chain.
     *
     * @param walker Reference to AxesWalker derivative, or may be null.
     */
    public void setNextWalker(AxesWalker walker) {
        m_nextWalker = walker;
    }

    /**
     * Get the previous walker reference in the location step chain.
     *
     * @return Reference to previous walker reference in the location
     * step chain, or null.
     */
    public AxesWalker getPrevWalker() {
        return m_prevWalker;
    }

    /**
     * Set or clear the previous walker reference in the location step chain.
     *
     * @param walker Reference to previous walker reference in the location
     *               step chain, or null.
     */
    public void setPrevWalker(AxesWalker walker) {
        m_prevWalker = walker;
    }

    /**
     * This is simply a way to bottle-neck the return of the next node, for
     * diagnostic purposes.
     *
     * @param n Node to return, or null.
     * @return The argument.
     */
    private int returnNextNode(int n) {

        return n;
    }

    /**
     * Get the next node in document order on the axes.
     *
     * @return the next node in document order on the axes, or null.
     */
    protected int getNextNode() {
        if (m_foundLast)
            return DTM.NULL;

        if (m_isFresh) {
            m_currentNode = m_traverser.first(m_root);
            m_isFresh = false;
        }
        // I shouldn't have to do this the check for current node, I think.
        // numbering\numbering24.xsl fails if I don't do this.  I think
        // it occurs as the walkers are backing up. -sb
        else if (DTM.NULL != m_currentNode) {
            m_currentNode = m_traverser.next(m_root, m_currentNode);
        }

        if (DTM.NULL == m_currentNode)
            this.m_foundLast = true;

        return m_currentNode;
    }

    /**
     * Moves the <code>TreeWalker</code> to the next visible node in document
     * order relative to the current node, and returns the new node. If the
     * current node has no next node,  or if the search for nextNode attempts
     * to step upward from the TreeWalker's root node, returns
     * <code>null</code> , and retains the current node.
     *
     * @return The new node, or <code>null</code> if the current node has no
     * next node  in the TreeWalker's logical view.
     */
    public int nextNode() {
        int        nextNode = DTM.NULL;
        AxesWalker walker   = wi().getLastUsedWalker();

        while (true) {
            if (null == walker)
                break;

            nextNode = walker.getNextNode();

            if (DTM.NULL == nextNode) {

                walker = walker.m_prevWalker;
            } else {
                if (walker.acceptNode(nextNode) != DTMIterator.FILTER_ACCEPT) {
                    continue;
                }

                if (null == walker.m_nextWalker) {
                    wi().setLastUsedWalker(walker);

                    // return walker.returnNextNode(nextNode);
                    break;
                } else {
                    AxesWalker prev = walker;

                    walker = walker.m_nextWalker;

                    walker.setRoot(nextNode);

                    walker.m_prevWalker = prev;

                    continue;
                }
            }  // if(null != nextNode)
        }  // while(null != walker)

        return nextNode;
    }

    /**
     * Get the index of the last node that can be itterated to.
     *
     * @param xctxt XPath runtime context.
     * @return the index of the last node that can be itterated to.
     */
    public int getLastPos(XPathContext xctxt) {

        int pos = getProximityPosition();

        AxesWalker walker;

        try {
            walker = (AxesWalker) clone();
        } catch (CloneNotSupportedException cnse) {
            return -1;
        }

        walker.setPredicateCount(m_predicateIndex);
        walker.setNextWalker(null);
        walker.setPrevWalker(null);

        WalkingIterator lpi         = wi();
        AxesWalker      savedWalker = lpi.getLastUsedWalker();

        try {
            lpi.setLastUsedWalker(walker);

            int next;

            while (DTM.NULL != (next = walker.nextNode())) {
                pos++;
            }

            // TODO: Should probably save this in the iterator.
        } finally {
            lpi.setLastUsedWalker(savedWalker);
        }

        // System.out.println("pos: "+pos);
        return pos;
    }

    /**
     * Set the DTM for this walker.
     *
     * @param dtm Non-null reference to a DTM.
     */
    public void setDefaultDTM(DTM dtm) {
        m_dtm = dtm;
    }

    /**
     * Get the DTM for this walker.
     *
     * @return Non-null reference to a DTM.
     */
    public DTM getDTM(int node) {
        //
        return wi().getXPathContext().getDTM(node);
    }

    /**
     * Returns true if all the nodes in the iteration well be returned in document
     * order.
     * Warning: This can only be called after setRoot has been called!
     *
     * @return true as a default.
     */
    public boolean isDocOrdered() {
        return true;
    }

    /**
     * Returns the axis being iterated, if it is known.
     *
     * @return Axis.CHILD, etc., or -1 if the axis is not known or is of multiple
     * types.
     */
    public int getAxis() {
        return m_axis;
    }

    /**
     * This will traverse the heararchy, calling the visitor for
     * each member.  If the called visitor method returns
     * false, the subtree should not be called.
     *
     * @param owner   The owner of the visitor, where that path may be
     *                rewritten if needed.
     * @param visitor The visitor whose appropriate method will be called.
     */
    public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) {
        if (visitor.visitStep(owner, this)) {
            callPredicateVisitors(visitor);
            if (null != m_nextWalker) {
                m_nextWalker.callVisitors(this, visitor);
            }
        }
    }

    /**
     * @see ExpressionOwner#getExpression()
     */
    public Expression getExpression() {
        return m_nextWalker;
    }

    /**
     * @see ExpressionOwner#setExpression(Expression)
     */
    public void setExpression(Expression exp) {
        exp.exprSetParent(this);
        m_nextWalker = (AxesWalker) exp;
    }

    /**
     * @see Expression#deepEquals(Expression)
     */
    public boolean deepEquals(Expression expr) {
        if (!super.deepEquals(expr))
            return false;

        AxesWalker walker = (AxesWalker) expr;
        if (this.m_axis != walker.m_axis)
            return false;

        return true;
    }
}
